#include <list>
#include <map>
#include <iostream>
#include <fstream>
#include "Renderer.h"
#include "ProjectHeader.h"
#include "CatalogueLoader.h"

#include "SOIL\src\SOIL.h"	// Header File For The SOIL Library
//using namespace System;


int Renderer::TestStatFunc(int i)
{
	return 75;
}	//TEST

			Renderer::Renderer()
{
	xView = 0.0f;
	depth = 0.0f;

	light = false;

	LightAmbient[0] = 0.5f; 
	LightAmbient[1] = 0.5f; 
	LightAmbient[2] = 0.5f; 
	LightAmbient[3] = 1.0f;

	LightDiffuse[0] = 1.0f; 
	LightDiffuse[1] = 1.0f;
	LightDiffuse[2] = 1.0f; 
	LightDiffuse[3] = 1.0f;

	LightPosition[0]= 0.0f; 
	LightPosition[1]= 0.0f; 
	LightPosition[2]= 2.0f; 
	LightPosition[3]= 1.0f;

}

int			Renderer::InitGL()							
{
	// All Setup For OpenGL Goes Here
	glEnable(GL_TEXTURE_2D);				// Enable Texture Mapping

	TextureLoader_delegate TexLoader(this, &Renderer::LoadTexture);	//Create a fastDelegate to LoadTexture
	if(CatalogueLoader::LoadTextureAtlases("Default.cat", TexLoader, TexCoords))	// Load up the texture atlases
	//TODO texture atlas name should be passed somehow
	{return TEX_LOAD_ERROR;}				// Or fail;

	if(!Texture.size()){return TEX_LOAD_ERROR;}	//Return a failure if no texture is loaded.

	if(glewInit() != GLEW_OK) {return GLEW_LOAD_ERROR;}						
	// If GLEW fails to init, return a fail.

	//if(GLEW_VERSION_1_5){return GL_VERSION_ERROR;}
	// GL 1.5 is required for VBOs; if it's not available, return a fail.

	glLightfv(GL_LIGHT1, GL_AMBIENT,  LightAmbient);
	glLightfv(GL_LIGHT1, GL_DIFFUSE,  LightDiffuse);
	glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHTING);

	glShadeModel(GL_SMOOTH);						// Enables Smooth Shading
	glClearDepth(1.0f);								// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);						// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);							// Use <= depth tests
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	
	// Really Nice Perspective Calculations
	glClearColor(0.7f, 0.9f, 1.0f, 1.0f);			// Set a Background Colour

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);	
	// Blend based on alpha value
	glEnable(GL_BLEND);
	char * Version = (char *) glGetString(GL_VERSION);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);	
	glEnableClientState(GL_COLOR_ARRAY);

	
	sceneCamera.SetZPos(-5l);
	sceneCamera.SetFOV(60.0l, 2.0f, 85.0l, 10.0f);
	sceneCamera.SetOrthoView(0.5f, 5.0f);
	//TODO: Maybe the camera z position, fovs and speeds should be parameters in the catalogue file


	vertex2f TestVertex2f;				//TEST
	TestVertex2f.u = 0.25f;				//TEST
	TestVertex2f.v = 0.25f;				//TEST
	UIStat_delegate TestDelegate(this, &Renderer::TestStatFunc);	//TEST

	DrawEvent_delegate UIDrawer(&TestUIElement, &UISliderElement::Draw);
	TestUIElement.SetSize(TestVertex2f);//TEST
	TestUIElement.SetPos(TestVertex2f);	//TEST
	TestUIElement.SetBackground(TexCoords[9]);	//TEST
	TestUIElement.SetSliderBackground(TexCoords[10]);
	TestUIElement.SetSliderTexture(TexCoords[11]);
	TestUIElement.SetSliderMode(clipFront);
	TestUIElement.SetSliderPosition(TestVertex2f);
	TestUIElement.SetSliderSize(TestVertex2f);
	TestUIElement.SetUIManDelegate(TestDelegate);	//TEST
	TestUIElement.SetStat(3);			//TEST
	
	RegisterUIDraw(UIDrawer, UPDATE_PER_FRAME);

	return 0;	// Initialization Went OK
	}

GLvoid		Renderer::ReSizeGLScene(GLsizei width, GLsizei height)
{
	if (height==0)							// Prevent A Divide By Zero By
	{height=1;}								// Making Height Equal One

	glViewport(0,0,width,height);			// Reset The cxCurrent Viewport

	glMatrixMode(GL_MODELVIEW);				// Select The Modelview Matrix
	glLoadIdentity();						// Reset The Modelview Matrix

	screenSize.u = width;
	screenSize.v = height;
	sceneCamera.SetScreenParams(screenSize, 0.1l, 500.0l);
	//TODO: Maybe the clipping planes should be positioned based on the grid size and such
}

void		Renderer::ReSizeGLScene(UINT msg, WPARAM wParam, LPARAM lParam)
{
	GLsizei width = LOWORD(lParam);			// The low word contains the width
	GLsizei height = HIWORD(lParam);		// The high word contains the height.
	ReSizeGLScene(width, height);			// Call overloaded ReSizeGLScene function
}

int			Renderer::DrawGLScene(GLvoid)
{
	// Here's Where We Do All The Drawing

	MakeVBODrawStore();
	MakeOverlayDrawStore();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear Screen And Depth Buffer
	glLoadIdentity();									// Reset the Modelview Matrix

	vertex2f Target; Target.u = xView; Target.v = depth;
	sceneCamera.UpdateTarget(Target, 0.0f);

	DrawVBODrawStore();
	DrawOverlayStore();

	//glDisableClientState(GL_VERTEX_ARRAY);
	//glDisableClientState(GL_NORMAL_ARRAY);
	//glDisableClientState(GL_TEXTURE_COORD_ARRAY);	
	//glDisableClientState(GL_COLOR_ARRAY);

	return TRUE;							// Everything Went OK
}

void		Renderer::DrawDDDT(std::map<int, Transform> &TransformCache, int DrawerNum, const DrawerDelegateDrawingType &DDDTIter)
{
	bool TransformApplied = false;

	if(DDDTIter.Transformer)
	{
		if(!TransformCache.count(DrawerNum))
		{
			TransformCache[DrawerNum] = DDDTIter.Transformer();
		}
		glPushMatrix();
		ApplyTransform(TransformCache[DrawerNum]);
		TransformApplied = true;
	}

	glBindBuffer(	GL_ARRAY_BUFFER_ARB, DDDTIter.VBO);
	glVertexPointer(	3,	GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 0);
	glNormalPointer(		GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 12);
	glTexCoordPointer(	2,	GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 24);
	glColorPointer(		4,	GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 32);
	glDrawArrays(GL_TRIANGLES, 0, DDDTIter.ElementCount);

	if(TransformApplied)
	{
		glPopMatrix();
	}
}
void		Renderer::DrawVBODrawStore()
{
	std::map<int, Transform> TransformCache;
	int currentTex = 0;
	int DrawerNum = 0;

	vertex2f Target; Target.u = xView; Target.v = depth;
    sceneCamera.UpdateTarget(Target, 0.0f);

	for(std::map<int, std::vector<DrawerDelegateDrawingType>>::iterator iter = VBODrawingStore.begin(); iter != VBODrawingStore.end(); ++iter)
	{
		DrawerNum = 0;
		currentTex = (*iter).first;
		if(CurrentBoundTex != currentTex){glBindTexture(GL_TEXTURE_2D, currentTex); CurrentBoundTex = currentTex;}
		for(std::vector<DrawerDelegateDrawingType>::iterator DDDTIter = (*iter).second.begin(); DDDTIter != (*iter).second.end(); ++DDDTIter)
		{
			if((*DDDTIter).VBO != NULL)
			{
				DrawDDDT(TransformCache, DrawerNum, (*DDDTIter));
			}
			++DrawerNum;
		}
	}
}
void		Renderer::DrawOverlayStore()
{
	std::map<int, Transform> TransformCache;
	int AtlasIndex = 0;
	int DrawerNum = 0;
	glClear(GL_DEPTH_BUFFER_BIT);
	sceneCamera.SetOrthoActive();

	for(std::map<int, std::vector<DrawerDelegateDrawingType>>::iterator iter = OverlayDrawingStore.begin(); iter != OverlayDrawingStore.end(); ++iter)
	{
		DrawerNum = 0;
		if(CurrentBoundTex != Texture[AtlasIndex]){glBindTexture(GL_TEXTURE_2D, Texture[AtlasIndex]); CurrentBoundTex = Texture[AtlasIndex];}
		for(std::vector<DrawerDelegateDrawingType>::iterator DDDTIter = (*iter).second.begin(); DDDTIter != (*iter).second.end(); ++DDDTIter)
		{
			if((*DDDTIter).VBO != NULL)
			{
				DrawDDDT(TransformCache, DrawerNum, (*DDDTIter));
			}
			++DrawerNum;
		}
		++AtlasIndex;
	}

}
int			Renderer::LoadTexture(const char *File)
{
	if(File == NULL){return NULL_FILE_ERROR;}
	Texture.push_back(SOIL_load_OGL_texture(File, 4, 0, SOIL_FLAG_POWER_OF_TWO));
	//TODO Look at other flags and whatnot for SOIL_load_OGL_texture
	if(Texture.back()){ return Texture.size() -1; }
	//Texture.size() - 1 is the last index; the one that we just put there.
	Texture.pop_back();	//Remove the entry that we added; There was an error.
	return SOIL_TEX_LOAD_ERROR;

}

GLvoid		Renderer::toggleLight()
{
	light = !light;
	if(light)
	{
		glEnable(GL_LIGHTING);
	}
	else
	{
		glDisable(GL_LIGHTING);
	}
}

GLvoid		Renderer::toggleBlend()
{
	blend = !blend;
	if(blend)
	{
		glEnable(GL_BLEND);
		glDisable(GL_DEPTH_TEST);
	}
	else
	{
		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
	}
}

bool		Renderer::RegisterDraw(DrawEventPair Drawer, enum DRAW_TYPE type)
{
	DrawerDelegateStorageType TempDDST;			//Create a temporary draw delegate storage type instance
	TempDDST.Drawer = Drawer.Draw;				//Fill it up...
	TempDDST.Transformer = Drawer.Transform;
	TempDDST.GlHint = type;
	VBODelegateStore.push_back(TempDDST);		//Add it to the list
	if(type = STATIC){redrawStatics = true;}
	return true;
}

bool		Renderer::RegisterDraw(DrawEvent_delegate Drawer, enum DRAW_TYPE type)
{
	DrawEventPair tempPair;
	tempPair.Transform = NULL;
	tempPair.Draw = Drawer;
	return (RegisterDraw(tempPair, type));
}

bool		Renderer::RegisterUIDraw(DrawEvent_delegate Drawer, enum DRAW_TYPE type)
{
	DrawEventPair tempPair;
	tempPair.Transform = NULL;
	tempPair.Draw = Drawer;
	return (RegisterUIDraw(tempPair, type));
}
bool		Renderer::RegisterUIDraw(DrawEventPair Drawer, enum DRAW_TYPE type)
{
	DrawerDelegateStorageType TempDDST;												//Create a temporary draw delegate storage type instance
	TempDDST.Drawer = Drawer.Draw;													//Fill it up...
	TempDDST.Transformer = Drawer.Transform;
	TempDDST.GlHint = type;
	OverlayDelegateStore.push_back(TempDDST);											//Add it to the list
	if(type = STATIC){redrawStatics = true;}
	return true;
}
void		Renderer::MakeDrawStore(const std::vector<DrawerDelegateStorageType> &DDSTvec, std::map<int, std::vector<DrawerDelegateDrawingType>> &DDDTmap)
{
	SizeDrawStore(DDSTvec, DDDTmap);
	int DrawerNo = 0;	//Keep track of the number of the drawer
	for(std::vector<DrawerDelegateStorageType>::const_iterator iter = DDSTvec.begin(); iter != DDSTvec.end(); ++iter)
	{
		for(unsigned int AtlasIndex = 0; AtlasIndex < Texture.size(); ++AtlasIndex)
		{
			if(
				( (*iter).GlHint == STATIC			&& !redrawStatics								) ||	//If it's static and redrawStatics is off
				  (*iter).GlHint == UPDATE_IF_DIRTY && !(*iter).Drawer(NULL, TexCoords, AtlasIndex	))		//or it's redraw_if_dirty and it's clean
			{continue;}
			std::vector<vertex> *TempVBO = new std::vector<vertex>;
			(*iter).Drawer(TempVBO, TexCoords, AtlasIndex);		//Takes AtlasIndex rather than the texture at AtlasIndexfor some reason that seemed good at the time.
			if(!TempVBO->size()){continue;}
			if(!DDDTmap[Texture[AtlasIndex]][DrawerNo].VBO)
			{
				glGenBuffersARB(1, &DDDTmap[Texture[AtlasIndex]][DrawerNo].VBO);			//Create the VBO if it doesn't exist for this combination of drawer and atlas
				DDDTmap[Texture[AtlasIndex]][DrawerNo].Transformer = (*iter).Transformer;	//Because it's new, we need to set the transformer too.
			}
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, DDDTmap[Texture[AtlasIndex]][DrawerNo].VBO);
			glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(vertex) * TempVBO->size(), &TempVBO->at(0).position.x, (*iter).GlHint);
			DDDTmap[Texture[AtlasIndex]][DrawerNo].ElementCount = TempVBO->size();
			delete TempVBO;	//Prevent memory leaks; Anything created with new sticks around even after the pointers to it fall out of scope.
		}
		++DrawerNo;
	}
	redrawStatics = false;
}

void		Renderer::MakeOverlayDrawStore()
{
	MakeDrawStore(OverlayDelegateStore, OverlayDrawingStore);
}
void		Renderer::MakeVBODrawStore()
{
	MakeDrawStore(VBODelegateStore, VBODrawingStore);
}
void		Renderer::SizeDrawStore(const std::vector<DrawerDelegateStorageType> &DDSTvec, std::map<int, std::vector<DrawerDelegateDrawingType>> &DDDTmap)
{
		for(unsigned int AtlasIndex = 0; AtlasIndex < Texture.size(); ++AtlasIndex)	//For every texture...
		{
			std::vector<DrawerDelegateDrawingType> TempDDDTLayer;
			TempDDDTLayer.resize(DDSTvec.size());
			if(!DDDTmap.count(Texture[AtlasIndex]))	//If there's no entry for drawers, add it
			{
				DDDTmap[Texture[AtlasIndex]] = TempDDDTLayer;
			}
			else if(DDDTmap[Texture[AtlasIndex]].size() < DDSTvec.size())	//if there's too few entries, add them
			{
				DDDTmap[Texture[AtlasIndex]].resize(DDSTvec.size());
			}
			else if(DDDTmap[Texture[AtlasIndex]].size() > DDSTvec.size())	//if there's too many, remove them
			{
				for(unsigned int i = DDSTvec.size(); i < DDDTmap[Texture[AtlasIndex]].size(); ++i)
				{
					glDeleteBuffers(1, &DDDTmap[Texture[AtlasIndex]][i].VBO);
					//Free up the VBO before we delete it, to prevent a memory leak.
				}
			}
		}

}

void		Renderer::ApplyTransform(const Transform &t)
{
	glTranslatef(t.Translation.x, t.Translation.y, t.Translation.z);
	glRotatef(t.Rotation.x, 1.0f, 0.0f, 0.0f);
	glRotatef(t.Rotation.y, 0.0f, 1.0f, 0.0f);
	glRotatef(t.Rotation.z, 0.0f, 0.0f, 1.0f);
}
const std::map<int, texCoord2f>& Renderer::getTexCoords()
{
	return TexCoords;
}